动态规划—递归与动态规划

1、概述

递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,
动态规划保存了子问题的解,避免重复计算。

动态规划一般可分为4类:

  • 线性动规(Sequence/Two Sequences DP)
  • 区域动规(Matrix DP)
  • 树形动规 (Tree DP)
  • 背包动规(Backpack)

2、矩阵类型

2.1 矩形路径最小和

public static int minPathSum(int[][] matrix) {
        int minPath = 0;
        if (matrix == null || matrix[0].length == 0 ||matrix.length == 0){
            return minPath;
        }
        int width = matrix[0].length;
        int height =  matrix.length;
        for (int i = 1; i < width; i++) {
            matrix[0][i] = matrix[0][i-1] + matrix[0][i];
        }
        for (int i = 1; i < height; i++) {
            matrix[i][0] = matrix[i-1][0] + matrix[i][0];
        }
        for (int i = 1; i < height ; i++) {
            for (int j = 1; j < width; j++) {
                matrix[i][j] = Math.min(matrix[i][j-1], matrix[i-1][j]) + matrix[i][j];
            }
        }
        return matrix[height-1][width-1];
    }

2.2 m*n棋盘走法

  • 递归实现
    给定一个M×N的格子或棋盘,从左下角走到右上角的走法总数(每次只能向右或向上移动一个方格边长的距离: f(m,n)=f(m-1,n)+f(m,n-1)

于是状态f(i,j)的状态转移方程为:
f(i,j)=f(i-1,j)+f(i,j-1) if i,j>0
f(i,j)=f(i,j-1) if i=0
f(i,j)=f(i-1,j) if j=0
f(0,0)=0, f(0,1)=1, f(1,0)=1

		public static int process(int m,int n){
			if(m==0&&n==0){
				return 0;
			}
			if(m==1||n==1){
				return 1;
			}
			return process(m,n-1)+process(m-1,n);
		}
  • 动态规划(不存在障碍物)
 // 不存在障碍物
 // 每个位置的个数= right + down
  public static int uniquePaths(int m, int n){
        int[][] dp = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
               dp[i][j]= i*j == 0 ? 1:dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
  • 动态规划(存在障碍物)
	// 躲避障碍物
	// 每个位置的个数= right + down
 	// 障碍时直接置为0
 	 public static int uniquePathsObstacleGrid(int[][] obstacleGrid) {
        if (obstacleGrid[0][0] == 1) {
            return 0;
        }
        int width = obstacleGrid[0].length;
        int height = obstacleGrid.length;

        // 构造dp,初始化
        int[][] dp = new int[height][width];
        dp[0][0] = 1;

        for (int i = 1; i < height; i++) {
            dp[i][0] = obstacleGrid[i][0] == 1? 0:dp[i-1][0];
        }
        for (int i = 1; i < width; i++) {
            dp[0][i] = obstacleGrid[0][i] == 1? 0:dp[0][i-1];
        }
        for (int i = 1; i < height; i++) {
            for (int j = 1; j < width; j++) {
                dp[i][j] = obstacleGrid[i][j] == 1? 0:dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[height-1][width-1];
    }

2、序列类型

2.1 爬楼梯

2.1.1 简单爬楼梯
  • 递归实现
    在这里插入图片描述
	// 时间复杂度为O(2^n)
	// 空间复杂度为O(n)
	public int fib( int n ){
    if( n == 0 )
        return 0;
    if( n == 1 )
        return 1;
    return fib(n-1) + fib(n-2);
}
  • 动态规划
    思路:n层楼梯,最后一步走完可能是1或2:f(n) = f(n-1) + f(n-2)

自己计算当楼梯数为1、2、3、4、5时,对应的爬法有:1、2、3、5、8、13、21种。 可以发现,随着楼梯数n的增加,爬法总数呈现斐波那契数列规律增加,
即f(n) = f(n-1) + f(n-2)

	// 时间复杂度O(n)
	// 空间复杂度O(1)
	public int fibonacci(int n) {
	    if (n == 0) 
	    	return 0;
	    if (n == 1 || n == 2) 
	    	return 1;
	    int f1 = 1, f2 = 1, fn;
	    for (int i = 3; i <= n; i++) {
	        fn = f1 + f2;
	        f1 = f2;
	        f2 = fn;
	    }
	    return fn;
	}
	
	// 或
   public  static int climbStairs2(int n){
        if (n == 1 || n ==  2){
            return n;
        }
        int[] dp = new int[n+1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n ; i++) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
2.1.2 跳到尾部
 public static boolean canJumps(int[] arr){
        if (arr.length == 0){
            return true;
        }
        boolean[] dp = new boolean[arr.length];
        dp[0] = true;

        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < i ; j++) {
                if (dp[j] && arr[j] + j >= i){
                    dp[i] = true;
                }
            }
        }
        return dp[arr.length-1];
    }

2.1.3 猴子吃桃子

/**
     *   n个桃子,每天吃掉一半多一个
     *
     *   第10天剩下1个,总共多少桃子
     *   n 为天数
     *
     *   f(n-1)= f(n)/2-1
     *
     *   f(n) = 2* f(n-1) + 2
     *
     */
    public static int monkeyQue(int x) {
        if (x == 0 || x == 1){
           return x;
        }
        int sum = 1;
        for (int i = 1; i <= x; i++) {
            sum = (sum+1)*2;
        }
        return sum;
    }

2.2 最长递增子序列长度

2.2.1 方法1(nlogn)
	初始时,nums=[10,9,2,5,3,7,101,18],res=[]
	i=0, res=[10]
	i=1, res=[9]
	i=2, res=[2]
	i=3, res=[2,5]
	i=4, res=[2,3]
	i=5, res=[2,3,7]
	i=6, res=[2,3,7,101]
	i=7, res=[2,3,7,18]
	
	小于替换大于添加

  public static int lengthOfLIS(int[] nums) {
            int len = nums.length;
            if (len <= 1) {
                return len;
            }
            int[] dp = new int[len];
            dp[0] = nums[0];
            int end = 0;
            for (int i = 1; i < len; i++) {
                if (nums[i] > dp[end]) {
                    end++;
                    dp[end] = nums[i];
                } else {
                    int left = 0;
                    int right = end;
                    while (left < right) {
                        int mid = left + ((right - left) >>> 1);
                        if (dp[mid] < nums[i]) {
                            left = mid + 1;
                        } else {
                            right = mid;
                        }
                    }
                    dp[left] = nums[i];
                }
            }
            end++;
            return end;
        }
2.2.2 方法2:dp
public static int lengthOfLIS(int[] arr){
        if (arr.length < 2){
            return arr.length;
        }
        int[] dp = new int[arr.length];
        dp[0] = 1;
        int res = 1;
        for (int i = 1; i < arr.length; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (arr[j] < arr[i]){
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }

2.3 字符串最长的回文长度

	1、中心扩展
	2、动态规划
	/**
     * 最长回文串-动态规划解法
     *     例:abcbcca
     *                  start   maxLen   curLen
     *
     *          a       0        1        1
     *          ab      1        1        2
     *
     * @return
     */
      public static String longestPalindrome(String str) {
        if (str == null || str.length() < 2) {
            return str;
        }
        int len = str.length();
        int start = 0;
        int maxLen = 1;

        boolean[][] dp = new boolean[len][len];
        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }


        for (int j = 1; j < len; j++) {
            for (int i = 0; i < j; i++) {
             /*   if (str.charAt(i) != str.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }*/
                 /*if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    start = i;
                }*/

                if (str.charAt(i) == str.charAt(j) && 
                ((j - i < 3) || dp[i + 1][j - 1])) {
                    dp[i][j] = true;
                    if (j - i + 1 > maxLen) {
                        maxLen = j - i + 1;
                        start = i;
                    }
                }
            }
        }

        return str.substring(start, start + maxLen);
    }	

3、两个子序列

3.1 最长公共子序列

	//   ' a d c e
    // ' 0 0 0 0 0
    // a 0 1 1 1 1
    // c 0 1 1 2 1
    
  public static int longestCommonSubsequence(String s1, String s2) {
        int len1 = s1.length();
        int len2 = s2.length();

        int[][] dp = new int[len1+1][len2+1];

        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                if (s1.charAt(i-1) == s2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1]+1;
                }else {
                    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[len1][len2];
    }    

4、背包问题

4.1 背包价值最大

先得到该问题的局部解然后扩展到全局问题解。
我们可以假设一个B(k,C) 方法,第k件物品,当前背包所剩下的容量C(初始则C=W)情况下,能够获取的最大价值量。

/**
 * 动态规划
 * 时间复杂度: O(n * C) 其中n为物品个数; C为背包容积
 * 空间复杂度: O(n * C)
 */
public class Solution02 {
    public int knapsack(int[] w, int[] v, int C) {
        int n = w.length;
        int[][] memo = new int[n][C + 1];

        if (n == 0 || C == 0)
            return 0;

        for (int j = 0; j <= C; j++)
            memo[0][j] = (j >= w[0] ? v[0] : 0);

        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= C; j++) {
                memo[i][j] = memo[i - 1][j];
                if (j >= w[i]) {
                    memo[i][j] = max(memo[i][j], v[i] + memo[i - 1][j - w[i]]);
                }
            }
        }

        return memo[n - 1][C];
    }

    private int max(int a, int b) {
        return a > b ? a : b;
    }


    public static void main(String[] args) {
        int[] w = {1, 2, 3};
        int[] v = {6, 10, 12};
        int C = 5;
        System.out.println(new Solution02().knapsack(w, v, C));
    }
}
public static int backPackII(int[] A,int[] V, int m){
        int[][] dp = new int[A.length+1][m+1];
        for (int i = 1; i <= A.length; i++) {
            for (int j = 0; j <= m; j++) {
                dp[i][j] = dp[i-1][j];
                if (j >= A[i-1] ){
                     dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-A[i-1]] + V[i-1]);
                }
            }
        }
        return dp[A.length][m];
    }

4.2 背包装最满值

		0 1 2 3 4 5 6 7 8 9 10  包大小

2		0 0 2 2 2 2 2 2 2 2 2
3       0 0 2 3 3 5 5 5 5 5 5
5       0 0 2 3 3 5 5 7 8 8 10

物品

// 注意点:每个物品只能选一次,不能重复选择同一个
 public static int backPack(int[] A, int m) {
        int[] dp = new int[m + 1];
        for (int i = 0; i < A.length; i++) {
            for (int j = m; j >= A[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j-A[i]] + A[i]);
            }
        }
        return dp[m];
    }

4.3 找零钱最小个数


    public static int coinChange(int[] coins, int amount){
        if (coins.length == 0 || amount == 0){
            return 0;
        }
        int[] dp = new int[amount+1];
        for (int i = 0; i <= amount ; i++) {
            dp[i] = amount+1;
        }
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (i >= coins[j]){
                    dp[i] = Math.min(dp[i-coins[j]]+1, dp[i]);
                }
            }
        }
        if (dp[amount] > amount){
            return -1;
        }
        return dp[amount];
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值